package com.echartsdata.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * 凉笙℡墨染
 * 2025-7-10
 * JWT令牌工具类 - 负责JWT令牌的生成、解析和验证
 * 核心功能：
 * 1. 生成JWT令牌（包含用户信息和过期时间）
 * 2. 从令牌中提取用户信息（如用户名、过期时间）
 * 3. 验证令牌的有效性和完整性
 * 4. 提供令牌刷新机制
 */
@Component
public class JwtTokenUtil {

    @Value("${jwt.secret}")
    private String secret; // JWT签名密钥（从配置文件中获取）

    @Value("${jwt.expiration}")
    private Long expiration; // 令牌过期时间（秒）

    /**
     * 获取签名密钥（HMAC-SHA256算法）
     * 将配置的字符串密钥转换为SecretKey对象
     */
    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }

    /**
     * 生成JWT令牌
     * @param userDetails Spring Security的用户详情对象
     * @return JWT令牌字符串
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(); // 自定义声明（可添加用户角色、权限等信息）
        return createToken(claims, userDetails.getUsername());
    }

    /**
     * 创建JWT令牌的核心方法
     * @param claims 自定义声明（Payload中的附加信息）
     * @param subject 主题（通常是用户名）
     * @return JWT令牌字符串
     */
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims) // 设置自定义声明
                .setSubject(subject) // 设置主题（用户名）
                .setIssuedAt(new Date(System.currentTimeMillis())) // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) // 设置过期时间
                .signWith(getSigningKey(), SignatureAlgorithm.HS256) // 使用HS256算法和密钥签名
                .compact(); // 生成紧凑的URL安全令牌字符串
    }

    /**
     * 验证令牌有效性
     * @param token JWT令牌
     * @param userDetails 用户详情对象
     * @return 令牌是否有效（用户名匹配且未过期）
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token); // 从令牌中提取用户名
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); // 验证用户名和过期时间
    }

    /**
     * 从令牌中提取用户名
     * @param token JWT令牌
     * @return 用户名
     */
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject); // 提取主题字段（用户名）
    }

    /**
     * 从令牌中提取过期时间
     * @param token JWT令牌
     * @return 过期日期对象
     */
    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration); // 提取过期时间字段
    }

    /**
     * 通用方法：从令牌中提取特定声明
     * @param token JWT令牌
     * @param claimsResolver 声明解析函数（如Claims::getSubject）
     * @param <T> 声明类型
     * @return 声明值
     */
    private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token); // 提取所有声明
        return claimsResolver.apply(claims); // 应用解析函数获取特定声明
    }

    /**
     * 解析JWT令牌，提取所有声明
     * @param token JWT令牌
     * @return 声明对象（包含所有Payload信息）
     */
    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey()) // 设置签名密钥（用于验证签名）
                .build()
                .parseClaimsJws(token) // 解析JWS（签名的JWT）
                .getBody(); // 获取Payload中的声明
    }

    /**
     * 检查令牌是否已过期
     * @param token JWT令牌
     * @return 令牌是否过期（true=已过期，false=未过期）
     */
    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date()); // 比较过期时间和当前时间
    }
}